{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 实验介绍\n",
    "\n",
    "## 实验背景\n",
    "\n",
    "恶性肿瘤俗称癌症，当前已经成为危害我国居民生命健康的主要杀手。应用中医药治疗恶性肿瘤已成为权威的治疗方法之一，且中医药治疗乳腺癌有着广泛的适应性和独特优势。在中医诊治的过程中，首先从整体出发，调整机体平衡，再根据**不同的临床证候**来进行辨证论治。确定“先证而治”的方向：即后续证候尚未出现之前，需要截断恶化病情的哪些后续证候。本实验使用三阴乳腺癌患者的临床患病信息数据来发现中医症状间的关联关系和诸多症状间的规律性，并且依据规则分析病因、预测病情发展以及为未来临床诊治提供有效借鉴。这样，在治疗患者的过程中，医生可以有效的减少西医治疗的毒副作用，为后续治疗打下基础。并且还能够帮助患者在术后更好的恢复体质，有利于提高患者的生存机率。\n",
    "## 挖掘目标\n",
    "本实验我们对三阴乳腺癌患者的病理信息进行数据挖掘。需要实现以下目标：\n",
    "1. 借助三阴乳腺癌患者的病理信息，挖掘患者的症状与中医证型之间的关联关系。\n",
    "2. 对截断治疗提供依据，挖掘潜性证素。\n",
    "\n",
    "目前，患者的临床病理信息大部分都记录在纸张上，包含了患者的基本信息、具体患病信息等，很少会将患者的患病信息存放于系统中，因此在对数据进行分析时会面临数据缺乏的情况。针对这种状况，我们采用问卷调查的方式收集数据；运用数据挖掘技术对收集的数据进行数据探索与预处理，形成建模数据；采用关联规则算法，挖掘各中医证素与乳腺癌TNM分期之间的关系。探索不同分期阶段的三阴乳腺癌患者的中医证素分布规律，以及采用截断病变发展、先期干预的治疗思路，指导三阴乳腺癌的中医临床治疗。\n",
    "\n",
    "在本实验中，我们接触了一种新的数据获取方式——问卷调查法。本次数据挖掘建模的总体流程如下：\n",
    "1. 以问卷调查的方法收集数据，并将问卷信息整理成原始数据\n",
    "2. 对原始数据集进行数据预处理，包括数据清洗、属性规约、数据变换\n",
    "3. 利用步骤2形成的建模数据，采用关联规则算法，调整模型输入参数，获取各中医证素 与乳腺癌TNM分期之间的关系。\n",
    "4. 结合实际业务，对模型结果进行分析，且将模型结果应用到实际业务中，最后输出关联规则结果。\n",
    "\n",
    "## 数据获取——问卷调查\n",
    "本实验采用调查问卷的形式对数据进行收集。以本实验为例简单介绍如何使用问卷调查的形式来收集数据。\n",
    "数据获取的具体过程如下：\n",
    "1. 拟定调查问卷表并形成原始指标表。\n",
    "2. 定义纳入标准与排除标准。\n",
    "3. 将收集回来的问卷表整理成原始数据。\n",
    "\n",
    "在制定问卷表时，首先根据中华中医药学会制定的相关指南与标注，从乳腺癌6种分型的症状中提取相应证素拟定调查问卷，并制定三阴乳腺癌中医证素诊断量表。从调查问卷中提炼信息形成原始属性表。然后依据标准定义表，将**有效**的问卷表整理成原始数据。\n",
    "问卷调查需要满足的两个条件：\n",
    "1. 问卷信息采集者均要求有中医诊断学基础，能准确识别病人的舌苔脉象，用通俗的语言解释医学术语，并确保患者信息填写准确。\n",
    "2. 问卷调查对象必须是三阴乳腺癌患者。\n",
    "\n",
    "## 数据预处理\n",
    "\n",
    "### 数据清洗\n",
    "\n",
    "由于本实验采用调查问卷的形式收集数据，应对收集到的调查问卷进行有效性筛选，收集者根据从医知识、日常知识等方面来对数据进行筛选。\n",
    "并将有效问卷整理成原始数据，共930条记录。\n",
    "整理后的部分原始数据集如下图。\n",
    "（插图）\n",
    "\n",
    "### 属性规约\n",
    "\n",
    "在上小节中看到本实验收集的数据的属性之多，为了更有效的进行数据挖掘，将其中冗余属性与挖掘任务不相关属性剔除。因此选取6中证型得分、TNM分期的属性值构成数据集。包括肝气郁结证得分、热毒蕴结证得分、冲任失调证得分、气血两虚证得分、脾胃虚弱证得分、肝肾阴虚证得分、TNM分期。\n",
    "\n",
    "### 数据变换\n",
    "\n",
    "本实验的数据变换主要采用属性构造和数据离散化两种方法对数据进行处理。首先通过属性构造，获得证型系数，然后通过聚类算法对数据进行离散化处理，形成建模数据。\n",
    "\n",
    "#### 属性构造\n",
    "由于数据得分的取值范围较大，计算时会由于部分值取值较大而占据不同的比重。为了更好的反映出中医证素分布的特征，采用证型系数代替具体单证型的证素得分，证型相关系数计算公式为： 证型系数 = 该证型得分 / 该证型总分\n",
    "\n",
    "#### 数据离散化\n",
    "对数据离散化的方法有：等宽法，等频法，聚类。\n",
    "由于Apriori关联规则算法无法处理连续型数值变量，因此需要将原始数据进行离散化，转换为适合建模的格式。\n",
    "本实验采用聚类算法对各个证型系数进行离散化处理，将每个属性聚为4类。并对其分类后的数据进行标记。\n",
    "新建文件discretization.py，添加如下代码："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#-*- coding: utf-8 -*-\n",
    "'''\n",
    "聚类离散化，最后的result的格式为：\n",
    "      1           2           3           4\n",
    "A     0    0.178698    0.257724    0.351843\n",
    "An  240  356.000000  281.000000   53.000000\n",
    "即(0, 0.178698]有240个，(0.178698, 0.257724]有356个，依此类推。\n",
    "'''\n",
    "from __future__ import print_function\n",
    "import pandas as pd\n",
    "from sklearn.cluster import KMeans #导入K均值聚类算法\n",
    "\n",
    "datafile = '../data/data.xls' #待聚类的数据文件\n",
    "processedfile = '../tmp/data_processed.xls' #数据处理后文件\n",
    "\"\"\"\n",
    "outfile = '../tmp/data_discret.xls' #离散化后的数据\n",
    "\n",
    "data = pd.read_excel(datafile)\n",
    "result = pd.read_excel(processedfile)\n",
    "\n",
    "df = data[['肝气郁结证型系数', '热毒蕴结证型系数', '冲任失调证型系数', '气血两虚证型系数', '脾胃虚弱证型系数', '肝肾阴虚证型系数']].copy()\n",
    "df.columns = ['A','B','C','D','E','F']\n",
    "\n",
    "df.loc[(df.A>result.iloc[0,0])&(df.A<result.iloc[0,1]),'Ax']= 'A1'\n",
    "df.loc[(df.A>result.iloc[0,1])&(df.A<result.iloc[0,2]),'Ax'] = 'A2'\n",
    "df.loc[(df.A>result.iloc[0,2])&(df.A<result.iloc[0,3]),'Ax'] = 'A3'\n",
    "df.loc[(df.A>result.iloc[0,3]),'Ax']= 'A4'\n",
    "\n",
    "df.loc[(df.B>result.iloc[2,0])&(df.B<result.iloc[2,1]),'Bx']= 'B1'\n",
    "df.loc[(df.B>result.iloc[2,1])&(df.B<result.iloc[2,2]),'Bx'] = 'B2'\n",
    "df.loc[(df.B>result.iloc[2,2])&(df.B<result.iloc[2,3]),'Bx'] = 'B3'\n",
    "df.loc[(df.B>result.iloc[2,3]),'Bx']= 'B4'\n",
    "\n",
    "df.loc[(df.C>result.iloc[4,0])&(df.C<result.iloc[4,1]),'Cx']= 'C1'\n",
    "df.loc[(df.C>result.iloc[4,1])&(df.C<result.iloc[4,2]),'Cx'] = 'C2'\n",
    "df.loc[(df.C>result.iloc[4,2])&(df.C<result.iloc[4,3]),'Cx'] = 'C3'\n",
    "df.loc[(df.C>result.iloc[4,3]),'Cx']= 'C4'\n",
    "\n",
    "df.loc[(df.D>result.iloc[6,0])&(df.D<result.iloc[6,1]),'Dx']= 'D1'\n",
    "df.loc[(df.D>result.iloc[6,1])&(df.D<result.iloc[6,2]),'Dx'] = 'D2'\n",
    "df.loc[(df.D>result.iloc[6,2])&(df.D<result.iloc[6,3]),'Dx'] = 'D3'\n",
    "df.loc[(df.D>result.iloc[6,3]),'Dx']= 'D4'\n",
    "\n",
    "df.loc[(df.E>result.iloc[8,0])&(df.E<result.iloc[8,1]),'Ex']= 'E1'\n",
    "df.loc[(df.E>result.iloc[8,1])&(df.E<result.iloc[8,2]),'Ex'] = 'E2'\n",
    "df.loc[(df.E>result.iloc[8,2])&(df.E<result.iloc[8,3]),'Ex'] = 'E3'\n",
    "df.loc[(df.E>result.iloc[8,3]),'Ex']= 'E4'\n",
    "\n",
    "df.loc[(df.F>result.iloc[10,0])&(df.F<result.iloc[10,1]),'Fx']= 'F1'\n",
    "df.loc[(df.F>result.iloc[10,1])&(df.F<result.iloc[10,2]),'Fx'] = 'F2'\n",
    "df.loc[(df.F>result.iloc[10,2])&(df.F<result.iloc[10,3]),'Fx'] = 'F3'\n",
    "df.loc[(df.F>result.iloc[10,3]),'Fx']= 'F4'\n",
    "\n",
    "df.to_excel(outfile)\n",
    "\n",
    "\"\"\"\n",
    "typelabel ={u'肝气郁结证型系数':'A', u'热毒蕴结证型系数':'B', u'冲任失调证型系数':'C', u'气血两虚证型系数':'D', u'脾胃虚弱证型系数':'E', u'肝肾阴虚证型系数':'F'}\n",
    "k = 4 #需要进行的聚类类别数\n",
    "\n",
    "#读取数据并进行聚类分析\n",
    "data = pd.read_excel(datafile) #读取数据\n",
    "keys = list(typelabel.keys())\n",
    "result = pd.DataFrame()\n",
    "\n",
    "if __name__ == '__main__': #判断是否主窗口运行，这句代码的作用比较神奇，有兴趣了解的读取请自行搜索相关材料。\n",
    "  for i in range(len(keys)):\n",
    "    #调用k-means算法，进行聚类离散化\n",
    "    print(u'正在进行“%s”的聚类...' % keys[i])\n",
    "    kmodel = KMeans(n_clusters = k, n_jobs = 1) #n_jobs是并行数，一般等于CPU数较好\n",
    "    kmodel.fit(data[[keys[i]]].as_matrix()) #训练模型\n",
    "    \n",
    "    r1 = pd.DataFrame(kmodel.cluster_centers_, columns = [typelabel[keys[i]]]) #聚类中心\n",
    "    #print(r1.columns,r1.values)\n",
    "    r2 = pd.Series(kmodel.labels_).value_counts() #分类统计\n",
    "    r2 = pd.DataFrame(r2, columns = [typelabel[keys[i]]+'n']) #转为DataFrame，记录各个类别的数目\n",
    "    #print(r2.columns,r2.values)\n",
    "    #r = pd.concat([r1, r2], axis = 1).sort(typelabel[keys[i]]) #匹配聚类中心和类别数目\n",
    "    r = pd.concat([r1, r2], axis = 1).sort_values(typelabel[keys[i]])\n",
    "    #print(r.columns,r.values)\n",
    "    r.index = [1, 2, 3, 4]\n",
    "    \n",
    "    #r[typelabel[keys[i]]] = pd.rolling_mean(r[typelabel[keys[i]]], 2) #rolling_mean()用来计算相邻2列的均值，以此作为边界点。\n",
    "    r[typelabel[keys[i]]] = r[typelabel[keys[i]]].rolling(2).mean()\n",
    "    r[typelabel[keys[i]]][1] = 0.0 #这两句代码将原来的聚类中心改为边界点。 \n",
    "    result = result.append(r.T)\n",
    "\n",
    "  #result = result.sort() #以Index排序，即以A,B,C,D,E,F顺序排\n",
    "  result = result.sort_values(by = list(result.index),axis=1) #以Index排序，即以A,B,C,D,E,F顺序排\n",
    "  result.to_excel(processedfile)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型构建\n",
    "\n",
    "本案例的目标是探索乳腺癌患者TNM分期与中医证型系数之间的关系，因此采用关联规则算法，挖掘他们之间的关联关系。\n",
    "关联规则算法主要用于寻找数据集中项之间的关联关系。在我们的前导课程《机器学习实战》中已经详细介绍了Apriori算法。它揭示了数据项间的未知关系，基于样本的统计规律，进行关联规则挖掘。根据所挖掘的关联关系，可以从一个属性的信息来推断另一个属性的信息。当置信度达到某一阈值时，就可以认为规则成立。\n",
    "\n",
    "目前，如何设置最小支持度与最小置信度，并没有统一的标准。大部分根据业务经验设置初始值，然后经过多次调整，获取与业务相符的关联规则结果。本实验选取模型的输入参数为：最小支持度6%、最小置信度75%。\n",
    "新建文件apriori_rules.py，添加如下代码：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "转换原始数据至0-1矩阵...\n",
      "\n",
      "转换完毕，用时：0.19秒\n",
      "\n",
      "开始搜索关联规则...\n",
      "\n",
      "正在进行第1次搜索...\n",
      "数目：276...\n",
      "\n",
      "正在进行第2次搜索...\n",
      "数目：947...\n",
      "\n",
      "正在进行第3次搜索...\n",
      "数目：41...\n",
      "\n",
      "结果为：\n",
      "                    support  confidence\n",
      "A3---F4---H4       0.078495    0.879518\n",
      "B2---F4---H4       0.062366    0.794521\n",
      "C2---E3---D2       0.092473    0.754386\n",
      "C3---F4---H4       0.075269    0.875000\n",
      "D2---F3---H4---A2  0.062366    0.753247\n",
      "\n",
      "搜索完成，用时：1.01秒\n"
     ]
    }
   ],
   "source": [
    "#-*- coding: utf-8 -*-\n",
    "from __future__ import print_function\n",
    "import pandas as pd\n",
    "#from apriori import * #导入自行编写的apriori函数\n",
    "import time #导入时间库用来计算用时\n",
    "\n",
    "#自定义连接函数，用于实现L_{k-1}到C_k的连接  \n",
    "#生成新的候选集为A---B\n",
    "def connect_string(x, ms):\n",
    "  #print(x)\n",
    "  x = list(map(lambda i:sorted(i.split(ms)), x))\n",
    "  #print(x)\n",
    "  l = len(x[0]) #目前单个候选集的元素个数\n",
    "  #print(l)\n",
    "  r = []\n",
    "  for i in range(len(x)):\n",
    "    for j in range(i,len(x)):\n",
    "      if x[i][:l-1] == x[j][:l-1] and x[i][l-1] != x[j][l-1]:#如果前k-1个相同，最后一个不同，则合并\n",
    "        r.append(x[i][:l-1]+sorted([x[j][l-1],x[i][l-1]]))\n",
    "  return r\n",
    "\n",
    "#寻找关联规则的函数\n",
    "def find_rule(d, support, confidence, ms = u'--'):\n",
    "  result = pd.DataFrame(index=['support', 'confidence']) #定义输出结果\n",
    "  \n",
    "  support_series = 1.0*d.sum()/len(d) #支持度序列 sum对列求和 len求总行数\n",
    "  column = list(support_series[support_series > support].index) #初步根据支持度筛选\n",
    "  k = 0\n",
    "  \n",
    "  while len(column) > 1:\n",
    "    k = k+1\n",
    "    print(u'\\n正在进行第%s次搜索...' %k)\n",
    "    column = connect_string(column, ms)\n",
    "    print(u'数目：%s...' %len(column))\n",
    "    sf = lambda i: d[i].prod(axis=1, numeric_only = True) #新一批支持度的计算函数\n",
    "    #对于同时存在以下几项特征的数据标记为1若['A1', 'A2']二者同时发生为1则此行积为1\n",
    "    #创建连接数据，这一步耗时、耗内存最严重。当数据集较大时，可以考虑并行运算优化。\n",
    "    #print(column)es\n",
    "    d_2 = pd.DataFrame(list(map(sf,column)), index = [ms.join(i) for i in column]).T#构建0-1矩阵\n",
    "    #print(d_2)\n",
    "    support_series_2 = 1.0*d_2[[ms.join(i) for i in column]].sum()/len(d) #计算连接后的支持度\n",
    "    #print(support_series_2)\n",
    "    column = list(support_series_2[support_series_2 > support].index) #新一轮支持度筛选\n",
    "    support_series = support_series.append(support_series_2)\n",
    "    column2 = []\n",
    "    \n",
    "    for i in column: #遍历可能的推理，如{A,B,C}究竟是A+B-->C还是B+C-->A还是C+A-->B？\n",
    "      i = i.split(ms)\n",
    "      for j in range(len(i)):\n",
    "        column2.append(i[:j]+i[j+1:]+i[j:j+1])#每个元素都当作结果元素\n",
    "\n",
    "    \n",
    "    cofidence_series = pd.Series(index=[ms.join(i) for i in column2]) #定义置信度序列\n",
    " \n",
    "    for i in column2: #计算置信度序列\n",
    "      cofidence_series[ms.join(i)] = support_series[ms.join(sorted(i))]/support_series[ms.join(i[:len(i)-1])]\n",
    "    \n",
    "    for i in cofidence_series[cofidence_series > confidence].index: #置信度筛选\n",
    "      result[i] = 0.0 #新增该列为float型\n",
    "      result[i]['confidence'] = cofidence_series[i]\n",
    "      result[i]['support'] = support_series[ms.join(sorted(i.split(ms)))]\n",
    "\n",
    "  #result = result.T.sort_values(['confidence','support'], ascending = False) #结果整理，输出\n",
    "  result = result.T.sort_index()\n",
    "  print(u'\\n结果为：')\n",
    "  print(result)\n",
    "  \n",
    "  return result\n",
    "\n",
    "if __name__ == '__main__':\n",
    "\n",
    "    inputfile = '../data/apriori.txt' #输入事务集文件\n",
    "    data = pd.read_csv(inputfile, header=None, dtype = object)\n",
    "\n",
    "    start = time.clock() #计时开始\n",
    "    print(u'\\n转换原始数据至0-1矩阵...')\n",
    "    ct = lambda x : pd.Series(1, index = x) #转换0-1矩阵的过渡函数\n",
    "    #b = map(ct, data.as_matrix()) #用map方式执行\n",
    "    b = map(ct, data.values) #用map方式执行\n",
    "    #data = pd.DataFrame(b).fillna(0) #实现矩阵转换，空值用0填充\n",
    "    #DataFrame中的元素不能是迭代的。\n",
    "    c = list(b)\n",
    "    data = pd.DataFrame(c).fillna(0)\n",
    "    end = time.clock() #计时结束\n",
    "    print(u'\\n转换完毕，用时：%0.2f秒' %(end-start))\n",
    "    del b #删除中间变量b，节省内存\n",
    "    del c\n",
    "\n",
    "    support = 0.06 #最小支持度\n",
    "    confidence = 0.75 #最小置信度\n",
    "    ms = '---' #连接符，默认'--'，用来区分不同元素，如A--B。需要保证原始表格中不含有该字符\n",
    "\n",
    "    start = time.clock() #计时开始\n",
    "    print(u'\\n开始搜索关联规则...')\n",
    "    find_rule(data, support, confidence, ms)\n",
    "    end = time.clock() #计时结束\n",
    "    print(u'\\n搜索完成，用时：%0.2f秒' %(end-start))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型分析\n",
    "\n",
    "根据上述运行结果，我们得出了5个关联规则。如A3--F4--H4,意为A3，F4=>H4。但是，并非所有的关联规则都有意义，由于我们将TNM的分期结果设为H标记。\n",
    "我们只在乎那些以H为规则结果的规则。\n",
    "其中每个关联规则都可以表示成X=>Y，其中X表示各个证型系数范围标识组合而成的规则，Y表示TNM分期为H4期（这里为H4是由于本实验中符合要求的规则结果均为H4）。A3表示肝气郁结证型系数处于(0.258,0.35]范围内的数值，其他的属性类型。\n",
    "\n",
    "分析规则模型结果表可得如下结论：\n",
    "1）A3、F4=>H4 支持度最大，达到7.85%，置信度最大，达到87.96%，说明肝气郁结证型系数处于(0.258,0.35]，肝肾阴虚证型系数处于(0.353,0.607]范围内，TNM分期诊断为H4期的可能性为87.96%，而这种情况发生的可能性为7.85%。\n",
    "2）C3、F4=>H4 支持度7.53%，置信度87.5%，说明冲任失调证型系数处于(0.201，0.288]，肝肾阴虚证型系数处于(0.353,0.607]范围内，TNM分期诊断为H4期的可能性为87.5%，而这种情况发生的可能性为7.53%。\n",
    "3）B2、F4=>H4 支持度6.24%，置信度79.45%，说明热毒蕴结证型系数处于(0.15，0.296]，肝肾阴虚证型系数处于(0.353,0.607]范围内，TNM分期诊断为H4期的可能性为79.45%，而这种情况发生的可能性为6.24%。\n",
    "\n",
    "综上分析，TNM分期为H4期的三阴乳腺癌患者证型主要为肝气郁结证、肝肾阴虚证、冲任失调、热毒蕴结证。其中肝气郁结证和肝肾阴虚证的临床表现较为突出，其置信度最大达到87.96%。\n",
    "\n",
    "对于模型结果，我们得出了TNM为H4期的主要临床特征。从医学角度分析：生理上，肝藏血，肾藏精，精血同源，肝肾同源。如《张氏医通》所言：气不耗，归精于肾而为精；精不泄，归精于肝而化清血。在病理上，肝肾病变常相互影响。H4期三阴乳腺癌患者多病程迁延，癌毒久蕴，故见肝肾阴虚之证。\n",
    "\n",
    "## 实验总结\n",
    "本实验中采用了调查问卷的形式收集数据，需要注意设计调查问卷时的细节问题。\n",
    "对于原始数据采用了聚类的手段来使数据离散化，更好的满足建模要求。\n",
    "使用了Apriori算法来发掘数据中的关联规则。\n",
    "最后对模型结果进行了与实际相关的分析。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
